Coverage Report

Created: 2026-06-19 16:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\csshw\csshw\src\protocol\deserialization.rs
Line
Count
Source
1
use windows::{
2
    core::BOOL,
3
    Win32::System::Console::{INPUT_RECORD_0, KEY_EVENT_RECORD, KEY_EVENT_RECORD_0},
4
};
5
6
use crate::protocol::{
7
    ClientState, DaemonToClientMessage, SERIALIZED_INPUT_RECORD_0_LENGTH, SERIALIZED_PID_LENGTH,
8
    TAG_HIGHLIGHT, TAG_INPUT_RECORD, TAG_KEEP_ALIVE, TAG_STATE_CHANGE,
9
};
10
11
/// Deserialize a [KEY_EVENT_RECORD_0] from a u8 slice using custom binary format.
12
///
13
/// Tries to read a u16 from the given slice in little-endian format.
14
///
15
/// Panics if reconstruction fails.
16
1
pub fn deserialize_key_event_record_0(slice: &[u8]) -> KEY_EVENT_RECORD_0 {
17
1
    return KEY_EVENT_RECORD_0 {
18
1
        UnicodeChar: u16::from_le_bytes([slice[0], slice[1]]),
19
1
    };
20
1
}
21
22
/// Deserialize a [KEY_EVENT_RECORD] from a u8 slice using custom binary format.
23
/// The slice is expected to be 13 bytes long.
24
///
25
/// Layout: [1 byte KeyDown][2 bytes RepeatCount][2 bytes VirtualKeyCode]
26
///         [2 bytes VirtualScanCode][2 bytes UnicodeChar][4 bytes ControlKeyState]
27
///
28
/// Panics if reconstruction fails.
29
6
pub fn deserialize_key_event_record(slice: &[u8]) -> KEY_EVENT_RECORD {
30
6
    return KEY_EVENT_RECORD {
31
6
        // KeyDown (1 byte)
32
6
        bKeyDown: BOOL::from(slice[0] != 0),
33
6
        // RepeatCount (2 bytes LE)
34
6
        wRepeatCount: u16::from_le_bytes([slice[1], slice[2]]),
35
6
        // VirtualKeyCode (2 bytes LE)
36
6
        wVirtualKeyCode: u16::from_le_bytes([slice[3], slice[4]]),
37
6
        // VirtualScanCode (2 bytes LE)
38
6
        wVirtualScanCode: u16::from_le_bytes([slice[5], slice[6]]),
39
6
        // UnicodeChar (2 bytes LE)
40
6
        uChar: KEY_EVENT_RECORD_0 {
41
6
            UnicodeChar: u16::from_le_bytes([slice[7], slice[8]]),
42
6
        },
43
6
        // ControlKeyState (4 bytes LE)
44
6
        dwControlKeyState: u32::from_le_bytes([slice[9], slice[10], slice[11], slice[12]]),
45
6
    };
46
6
}
47
48
/// Deserialize an [INPUT_RECORD_0].`KeyEvent` from a u8 slice using custom binary format.
49
///
50
/// Panics if reconstruction fails.
51
5
pub fn deserialize_input_record_0(slice: &[u8]) -> INPUT_RECORD_0 {
52
5
    let key_event = deserialize_key_event_record(slice);
53
5
    return INPUT_RECORD_0 {
54
5
        KeyEvent: key_event,
55
5
    };
56
5
}
57
58
/// Deserialize a process id from its little-endian byte representation used
59
/// by the named-pipe PID handshake.
60
10
pub fn deserialize_pid(bytes: &[u8; SERIALIZED_PID_LENGTH]) -> u32 {
61
10
    return u32::from_le_bytes(*bytes);
62
10
}
63
64
/// Deserialize a single byte into a [`ClientState`] variant.
65
///
66
/// # Arguments
67
///
68
/// * `byte` - The single payload byte of a [`crate::protocol::TAG_STATE_CHANGE`]
69
///            frame, equal to a [`ClientState`]'s `#[repr(u8)]` discriminant.
70
///
71
/// # Returns
72
///
73
/// The decoded [`ClientState`].
74
///
75
/// # Panics
76
///
77
/// Panics if `byte` does not match a known [`ClientState`] discriminant. An
78
/// unknown value indicates either a protocol-version mismatch between the
79
/// daemon and client or corruption on the pipe - both unrecoverable, matching
80
/// the codebase's "broken bookkeeping -> panic" convention.
81
6
pub fn deserialize_client_state(byte: u8) -> ClientState {
82
6
    match byte {
83
6
        
x3
if x == ClientState::Active as u8 => return
ClientState::Active3
,
84
3
        
x2
if x == ClientState::Disabled as u8 => return
ClientState::Disabled2
,
85
1
        other => panic!("Unknown ClientState byte: 0x{other:02X}"),
86
    }
87
5
}
88
89
/// Deserialize a single byte into a highlight flag.
90
///
91
/// # Arguments
92
///
93
/// * `byte` - Payload byte of a [`crate::protocol::TAG_HIGHLIGHT`]
94
///            frame: `0` for not highlighted, `1` for highlighted.
95
///
96
/// # Returns
97
///
98
/// `true` if the byte signals a highlighted client, `false` otherwise.
99
///
100
/// # Panics
101
///
102
/// Panics on any byte other than `0` or `1` (protocol mismatch or
103
/// pipe corruption).
104
6
pub fn deserialize_highlight(byte: u8) -> bool {
105
6
    match byte {
106
2
        0 => return false,
107
3
        1 => return true,
108
1
        other => panic!("Unknown highlight byte: 0x{other:02X}"),
109
    }
110
5
}
111
112
/// Parse as many complete [`DaemonToClientMessage`]s as possible from `buffer`.
113
///
114
/// The parser walks `buffer` from the start, decoding one tag-prefixed frame
115
/// at a time. Parsing stops when fewer bytes remain than are needed to
116
/// complete the next frame; the unconsumed tail is returned so the caller
117
/// can prepend it to the next read.
118
///
119
/// # Arguments
120
///
121
/// * `buffer` - Bytes received from the daemon's named pipe, possibly
122
///              including a partial trailing frame.
123
///
124
/// # Returns
125
///
126
/// A tuple of `(messages, remainder)` where `messages` are the fully decoded
127
/// frames in arrival order and `remainder` holds the unconsumed bytes (an
128
/// empty `Vec` if the buffer ended on a frame boundary).
129
///
130
/// # Panics
131
///
132
/// Panics if `buffer` contains a tag byte that is not part of the documented
133
/// daemon-to-client protocol (see [`crate::protocol`]). An unknown tag
134
/// indicates either a protocol-version mismatch between the daemon and
135
/// client or corruption on the pipe -- both unrecoverable, matching the
136
/// codebase's "broken bookkeeping -> panic" convention.
137
12
pub fn parse_daemon_to_client_messages(buffer: &[u8]) -> (Vec<DaemonToClientMessage>, Vec<u8>) {
138
12
    let mut messages: Vec<DaemonToClientMessage> = Vec::new();
139
12
    let mut pos = 0usize;
140
25
    while pos < buffer.len() {
141
15
        let tag = buffer[pos];
142
15
        match tag {
143
            TAG_INPUT_RECORD => {
144
5
                let payload_start = pos + 1;
145
5
                let payload_end = payload_start + SERIALIZED_INPUT_RECORD_0_LENGTH;
146
5
                if buffer.len() < payload_end {
147
                    // Trailing partial frame; stop and return it as remainder.
148
1
                    break;
149
4
                }
150
4
                let record = deserialize_input_record_0(&buffer[payload_start..payload_end]);
151
4
                messages.push(DaemonToClientMessage::InputRecord(record));
152
4
                pos = payload_end;
153
            }
154
            TAG_STATE_CHANGE => {
155
3
                let payload_index = pos + 1;
156
3
                if buffer.len() <= payload_index {
157
                    // Trailing partial frame; stop and return it as remainder.
158
0
                    break;
159
3
                }
160
3
                let state = deserialize_client_state(buffer[payload_index]);
161
3
                messages.push(DaemonToClientMessage::StateChange(state));
162
3
                pos = payload_index + 1;
163
            }
164
            TAG_HIGHLIGHT => {
165
3
                let payload_index = pos + 1;
166
3
                if buffer.len() <= payload_index {
167
                    // Trailing partial frame; stop and return it as remainder.
168
0
                    break;
169
3
                }
170
3
                let highlighted = deserialize_highlight(buffer[payload_index]);
171
3
                messages.push(DaemonToClientMessage::Highlight(highlighted));
172
3
                pos = payload_index + 1;
173
            }
174
3
            TAG_KEEP_ALIVE => {
175
3
                messages.push(DaemonToClientMessage::KeepAlive);
176
3
                pos += 1;
177
3
            }
178
            _ => {
179
1
                panic!("Unknown daemon-to-client message tag: 0x{tag:02X}");
180
            }
181
        }
182
    }
183
11
    return (messages, buffer[pos..].to_vec());
184
11
}